package api.betfairAPI;

import api.API;
import api.Market;
import com.betfair.www.publicapi.types.exchange.v5.GetMarketPricesCompressedReq;
import com.betfair.www.publicapi.types.exchange.v5.GetMarketPricesCompressedResp;
import com.betfair.www.publicapi.types.exchange.v5.GetMarketReq;
import com.betfair.www.publicapi.types.exchange.v5.GetMarketResp;
import com.betfair.www.publicapi.types.global.v3.*;
import com.google.common.base.Optional;
import org.apache.axis2.AxisFault;

import java.io.IOException;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.util.ArrayList;

import org.apache.log4j.Logger;
import utils.Display;

/**
 * User: Darren
 * Date: 5/01/13
 * Time: 9:56 AM
 */
public class BetfairAPI implements API {
    private static Logger logger = Logger.getLogger(BetfairAPI.class);
    private BetfairAPIStub betfairAPIStub = new BetfairAPIStub();

    protected boolean loggedIn = false;
    private BetfairStringHelper stringHelper = new BetfairStringHelper();
    private BetfairParseMarket parseMarket = new BetfairParseMarket();
    private BetfairAPICallRate betfairAPICallRate;
    private Optional<BetfairExchangeMarket> selectedExchangeMarketOptional = Optional.absent();

    public BetfairAPI(BetfairAPICallRate betfairAPICallRate) throws AxisFault {
        this.betfairAPICallRate = betfairAPICallRate;
    }

    @Override
    public boolean login(Display display) {
        logger.info("Betfair login initiated");
        if (loggedIn) {
            return true;
        }
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.LOGIN)) {
            return false;
        }
        String username;
        String password;
        try {
            username = display.getStringAnswer("Betfair username: ");
            password = display.getStringAnswer("Betfair password: ");
        } catch (IOException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return false;
        }
        LoginReq req = new LoginReq();
        req.setUsername(username);
        req.setPassword(password);
        req.setProductId(betfairAPICallRate.getProductId());
        req.setIpAddress("0");
        LoginResp resp;
        try {
            resp = betfairAPIStub.getGlobalServiceStub().login(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return false;
        }
        loggedIn = betfairAPIStub.responseValid(resp);
        if (loggedIn) {
            logger.info("Betfair login successful");
        }
        return loggedIn;
    }

    @Override
    public boolean logout(Display display) {
        logger.info("Betfair logout initiated");
        if (!loggedIn) {
            return true;
        }
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.LOGOUT)) {
            return false;
        }
        LogoutReq req = new LogoutReq();
        betfairAPIStub.initGlobalRequest(req);
        LogoutResp resp;
        try {
            resp = betfairAPIStub.getGlobalServiceStub().logout(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return false;
        }
        loggedIn = betfairAPIStub.responseValid(resp);
        if (!loggedIn) {
            logger.info("Betfair logout successful");
        }
        return !loggedIn;
    }

    @Override
    public boolean selectMarket(Market market, Display display) {
        logger.info("Betfair selectMarket initiated");
        BetfairMarket betfairMarket = typeCastMarket(market);
        selectedExchangeMarketOptional = selectMarket(display);
        if (!selectedExchangeMarketOptional.isPresent()) {
            return false;
        }
        if (getMarketPricesCompressed(betfairMarket)) {
            logger.info("Betfair selectMarket successful");
        }
        return true;
    }

    @Override
    public boolean getMarketPrices(Market market) {
        logger.info("Betfair getMarketPrices initiated");
        BetfairMarket betfairMarket = typeCastMarket(market);
        if (!selectedExchangeMarketOptional.isPresent()) {
            return false;
        }
        if (getMarketPricesCompressed(betfairMarket)) {
            logger.info("Betfair getMarketPrices successful");
        }
        return true;
    }

    public void setBetfairExchangeMarket(BetfairExchangeMarket betfairExchangeMarket) {
        this.selectedExchangeMarketOptional = Optional.of(betfairExchangeMarket);
    }

    private BetfairMarket typeCastMarket(Market market) {
        if (!(market instanceof BetfairMarket)) {
            String errorMessage = "Market parameter supplied to BetfairAPI selectMarket is of type: " +
                    market.getClass();
            logger.fatal(errorMessage);
            throw new RuntimeException(errorMessage);
        }
        return (BetfairMarket) market;
    }

    private Optional<EventType[]> getActiveEventType() {
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.GET_ACTIVE_EVENT_TYPES)) {
            Optional.absent();
        }
        GetEventTypesReq req = new GetEventTypesReq();
        betfairAPIStub.initGlobalRequest(req);
        GetEventTypesResp resp;
        try {
            resp = betfairAPIStub.getGlobalServiceStub().getActiveEventTypes(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return Optional.absent();
        }
        if (!betfairAPIStub.responseValid(resp)) {
            logger.error("Betfair getActiveEventTypes response exception");
            return Optional.absent();
        }
        return Optional.of(resp.getEventTypeItems().getEventType());
    }

    // TODO: this method is too long
    private Optional<BetfairExchangeMarket> selectEventMarket(int eventId, Display display) {
        Optional<BetfairExchangeMarket> exchangeMarketOptional = Optional.absent();
        int selection;
        do {
            Optional<GetEventsResp> respOptional = getEvents(eventId);
            if (!respOptional.isPresent()) {
                return Optional.absent();
            }
            BFEvent[] events = filterEvents(respOptional.get());
            MarketSummary[] markets = respOptional.get().getMarketItems().getMarketSummary();
            if (markets == null) {
                markets = new MarketSummary[] {};
            }
            String[] eventsMenuItems = stringHelper.stringsToMenuItems(stringHelper.bFEventToString(events));
            String[] marketMenuItems = stringHelper.stringsToMenuItems(stringHelper.marketSummaryToString(markets),
                    eventsMenuItems.length);
            try {
                if (eventsMenuItems.length > 0) {
                    if (marketMenuItems.length > 0) {
                        selection = display.getChoiceAnswer("Choose a Betfair market or event [-1 to quit]: ",
                                stringHelper.getEventFieldNames(), eventsMenuItems,
                                stringHelper.getMarketFieldNames(), marketMenuItems,
                                -1, eventsMenuItems.length + marketMenuItems.length -1);
                    } else {
                        selection = display.getChoiceAnswer("Choose a Betfair event [-1 to quit]: ",
                                stringHelper.getEventFieldNames(), eventsMenuItems,
                                -1, eventsMenuItems.length -1);
                    }
                } else {
                    selection = display.getChoiceAnswer("Choose a Betfair market [-1 to quit]: ",
                            stringHelper.getMarketFieldNames(), marketMenuItems,
                            -1, marketMenuItems.length -1);
                }
            } catch (IOException e) {
                logger.error(e.toString());
                e.printStackTrace();
                return Optional.absent();
            }
            if (selection >= 0 && selection < eventsMenuItems.length) {
                exchangeMarketOptional = selectEventMarket(events[selection].getEventId(), display);
            } else if (selection >= eventsMenuItems.length) {
                selection -= eventsMenuItems.length;
                exchangeMarketOptional = getMarket(markets[selection].getExchangeId(), markets[selection].getMarketId());
            }
        } while (selection >= 0 && !exchangeMarketOptional.isPresent());
        return exchangeMarketOptional;
    }

    private Optional<GetEventsResp> getEvents(int eventId) {
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.GET_EVENTS)) {
            return Optional.absent();
        }
        GetEventsReq req = new GetEventsReq();
        betfairAPIStub.initGlobalRequest(req);
        req.setEventParentId(eventId);
        GetEventsResp resp;
        try {
            resp = betfairAPIStub.getGlobalServiceStub().getEvents(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return Optional.absent();
        }
        if (!betfairAPIStub.responseValid(resp)) {
            logger.error("Betfair getEvents response exception");
            return Optional.absent();
        }
        return Optional.of(resp);
    }

    private BFEvent[] filterEvents(GetEventsResp resp) {
        BFEvent[] events = resp.getEventItems().getBFEvent();
        if (events == null) {
            return new BFEvent[] {};
        }
        ArrayList<BFEvent> nonCouponEvents = new ArrayList<BFEvent>(events.length);
        for (BFEvent event : events) {
            if (!event.getEventName().equals("Coupons")) {
                nonCouponEvents.add(event);
            }
        }
        return nonCouponEvents.toArray(new BFEvent[] {});
    }

    private Optional<BetfairExchangeMarket> getMarket(int exchangeId, int marketId) {
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.GET_MARKET)) {
            return Optional.absent();
        }
        GetMarketReq req = new GetMarketReq();
        betfairAPIStub.initExchRequest(req);
        req.setMarketId(marketId);
        GetMarketResp resp;
        try {
            resp = betfairAPIStub.getExchangeServiceStub(exchangeId).getMarket(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return Optional.absent();
        }
        if (!betfairAPIStub.responseValid(resp)) {
            logger.error("Betfair getMarket response exception");
            return Optional.absent();
        }
        return Optional.of(new BetfairExchangeMarket(resp.getMarket(), exchangeId));
    }

    private Optional<BetfairExchangeMarket> selectMarket(Display display) {
        Optional<BetfairExchangeMarket> exchangeMarketOptional = Optional.absent();
        int selection;
        do {
            Optional<EventType[]> eventTypesOptional = getActiveEventType();
            if (!eventTypesOptional.isPresent()) {
                return Optional.absent();
            }
            String[] menuItems = stringHelper.stringsToMenuItems(stringHelper.eventTypesToStrings(
                    eventTypesOptional.get()));
            try {
                selection = display.getChoiceAnswer("Choose an active Betfair event type [-1 to quit]: ", menuItems,
                        -1, menuItems.length -1);
            } catch (IOException e) {
                logger.error(e.toString());
                e.printStackTrace();
                return Optional.absent();
            }
            if (selection >= 0 && selection < menuItems.length) {
                exchangeMarketOptional = selectEventMarket(eventTypesOptional.get()[selection].getId(), display);
            }
        } while (selection >= 0 && exchangeMarketOptional == null);
        return exchangeMarketOptional;
    }

    private boolean getMarketPricesCompressed(BetfairMarket market) {
        if (betfairAPICallRate.callExceedsLimit(BetfairAPICallTypes.GET_MARKET_PRICES_COMPRESSED)) {
            return false;
        }
        if (!selectedExchangeMarketOptional.isPresent()) {
            logger.fatal("An exchange and market have not been selected for this API");
            return false;
        }
        BetfairExchangeMarket selectedExchangeMarket = selectedExchangeMarketOptional.get();
        GetMarketPricesCompressedReq req = new GetMarketPricesCompressedReq();
        betfairAPIStub.initExchRequest(req);
        req.setMarketId(selectedExchangeMarket.getMarket().getMarketId());
        GetMarketPricesCompressedResp resp;
        try {
            resp = betfairAPIStub.getExchangeServiceStub(selectedExchangeMarket.getExchangeId())
                    .getMarketPricesCompressed(req);
        } catch (RemoteException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return false;
        }
        if (!betfairAPIStub.responseValid(resp)) {
            logger.error("Betfair getMarketPricesCompressed response exception");
            return false;
        }
        try {
            parseMarket.parseBetfairExchangeMarket(resp.getMarketPrices(), selectedExchangeMarket, market);
        } catch (ParseException e) {
            logger.error(e.toString());
            e.printStackTrace();
            return false;
        }
        return true;
    }
}
